﻿using System;
using System.Collections.Generic;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.ObjectRepository;
using OverStore.Runtime.Storage;
using OverStore.Runtime.Logger;
using OverStore.Runtime.ObjectRepository.IdentifierProvider;

namespace OverStore.Runtime.ObjectRepository.Reader
{

    /// <summary>
    /// Reads sequence of objects of <typeparamref name="TInstance"/> type.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    /// <typeparam name="TIdentifier">The type of the identifier.</typeparam>
    /// <remarks>
    /// For each new row of data read from storage read variant at first extracts
    /// identifier using specified <see cref="IIdentifierProvider{TIdentifier}"/>.
    /// Then using instance of <see cref="IEntityRegistry{TInstance, TIdentifier}"/> it obtain instance with
    /// extracted identifier. If <paramref name="useResultSetForObjectConstruction"/> read variant specifies
    /// additional argument to IRegistry.GetInstance method.
    /// This argument allows registry to use specified data for load instance requested by read variant.
    /// It significantly improves performance of reading.
    /// </remarks>
    public sealed class ParameterlessReader<TInstance, TIdentifier> : IReader<TInstance, TIdentifier>
        where TInstance : class
    {
        private readonly IIdentifierProvider<TIdentifier> _IdentifierProvider;
        private readonly ICommand _ReadCommand;
        private readonly IDataStorage _Storage;
        private readonly bool _UseResultSetForObjectConstruction;
        private readonly IPersistenceServiceProvider _Provider;
        private readonly ILogger _Logger;

        /// <summary>
        /// Initializes a new instance of the <see cref="ParameterlessReader{TItem, TItemIdentifier}"/> class.
        /// </summary>
        /// <param name="provider">Object provides access to different services that allows to perform different task regarding persisting instance of specified type.</param>
        /// <param name="storage">The data storage used for extract data.</param>
        /// <param name="readCommand">The <see cref="ICommand"/> used for access data storage.</param>
        /// <param name="identifierProvider">The identifier provider uses for obtain instance identifiers from data.</param>
        /// <param name="logger">Logger used to output diagnostics information.</param>
        /// <param name="useResultSetForObjectConstruction">If set to <c>true</c> ExecuteRead method uses
        /// <see cref="IRecord"/> returned by <see cref="IDataStorage.ReadAll"/> for constructing instances of <typeparamref name="TItem"/>;
        /// otherwise method uses only identifier for access instances via <see cref="IEntityRegistry{TInstance, TIdentifier}"/>.</param>
        /// <exception cref="ArgumentNullException"><paramref name="provider"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="readCommand"/> is null reference.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="identifierProvider"/> is null reference.</exception>
        public ParameterlessReader(IPersistenceServiceProvider provider, IDataStorage storage, ICommand readCommand, IIdentifierProvider<TIdentifier> identifierProvider, ILogger logger,
            bool useResultSetForObjectConstruction)
        {
            if (Object.ReferenceEquals(provider, null))
                throw new ArgumentNullException("provider");
            _Provider = provider;

            if (Object.ReferenceEquals(readCommand, null))
                throw new ArgumentNullException("readCommand");
            _ReadCommand = readCommand;

            _UseResultSetForObjectConstruction = useResultSetForObjectConstruction;

            if (identifierProvider == null)
                throw new ArgumentNullException("identifierProvider");
            _IdentifierProvider = identifierProvider;
            if (storage == null)
                throw new ArgumentNullException("storage");
            _Storage = storage;
            _Logger = logger;
        }

        /// <summary>
        /// Reads object sequence from data storage.
        /// </summary>
        /// <returns>
        /// The sequence of object read from data storage.
        /// </returns>
        public IEnumerable<TInstance> ExecuteRead()
        {
            return new ResultSetEnumerable<TInstance, TIdentifier>(
                        _ReadCommand,
                        _Storage,
                        EmptyTypedRecord.Value,
                        _IdentifierProvider,
                        _Logger,
                        _UseResultSetForObjectConstruction,
                        _Provider);
        }

        /// <summary>
        /// Gets the type of the parameter for reader or null if reader does not expect any parameter.
        /// </summary>
        /// <value>The type of the parameter.</value>
        Type IReader<TInstance, TIdentifier>.ParameterType
        {
            get { return null; }
        }

        /// <summary>
        /// Executes the reader with specified parameters. <paramref name="parameter"/> may be null
        /// if implementation supports it.
        /// </summary>
        /// <param name="parameter">The parameter.</param>
        /// <returns></returns>
        IEnumerable<TInstance> IReader<TInstance, TIdentifier>.ExecuteReader(object parameter)
        {
            return ExecuteRead();
        }
    }
}
